﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using EnvDTE;
using Microsoft.VisualStudio.SharePoint;
using SPVSX.Common;

namespace SPVSX.DeploymentExtensions.QuickDeployment
{
    /// <summary>
    /// TODO: COMMENT
    /// 
    /// This code is a WIP and is in need of improved commenting, exception handling, and potentially
    /// factoring.  Please be nice.
    /// 
    /// Though this code is Open Source, in good faith it is requested that you consult the original author(s)
    /// before changing or re-using it for any public endeavour intended to provide similar functionality, or
    /// to fix simple bugs.  In other words, if you want to create SPVSX2, please let us know first.
    /// </summary>
    /// <remarks>@author Matt Smith, http://blog.mattsmith.co.nz</remarks>
    public class QuickDeploymentUtilities
    {
        private static readonly string[] DefaultTokenizedFiles = new string[] { ".xml", ".ascx", ".aspx", ".webpart", ".dwp" };

        /// <summary>
        /// Returns all SharePoint artefacts for a given ProjectItem.  Multiple items may be returned, for example, if the
        /// selection is a folder.  Note that this list is not pruned in the event of multiple selections from the same sub-tree.
        /// </summary>
        /// <param name="projectService"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public static List<QuickCopyableSharePointArtefact> ResolveProjectItemToArtefacts(ISharePointProjectService projectService, ProjectItem item)
        {
            // Ensure we have a SharePoint service.
            if (projectService == null)
            {
                return null;
            }

            // Prepare list for return.
            List<QuickCopyableSharePointArtefact> artefacts = new List<QuickCopyableSharePointArtefact>();

            // See if this item is a SPI file.
            try
            {
                ISharePointProjectItemFile spFile = projectService.Convert<ProjectItem, ISharePointProjectItemFile>(item);
                if (spFile != null)
                {
                    if (spFile.DeploymentType != DeploymentType.NoDeployment)
                    {
                        artefacts.Add(new SharePointProjectItemFileArtefact(spFile));
                        return artefacts;
                    }
                }
            }
            catch { }

            // See if this item is an SPI.
            try
            {
                ISharePointProjectItem spItem = projectService.Convert<ProjectItem, ISharePointProjectItem>(item);
                if (spItem != null)
                {
                    artefacts.Add(new SharePointProjectItemArtefact(spItem));
                    return artefacts;
                }
            }
            catch { }

            // See if this item is a Feature.
            try
            {
                ISharePointProjectFeature spFeature = projectService.Convert<ProjectItem, ISharePointProjectFeature>(item);
                if (spFeature != null)
                {
                    artefacts.Add(new SharePointProjectFeatureArtefact(spFeature));
                    return artefacts;
                }
            }
            catch { }

            // See if we have a Folder, and recursively find SharePoint items.
            try
            {
                if (item.ProjectItems.Count > 0)
                {
                    for (int i = 1; i <= item.ProjectItems.Count; i++)
                    {
                        ProjectItem childItem = item.ProjectItems.Item(i);
                        artefacts.AddRange(ResolveProjectItemToArtefacts(projectService, childItem));
                    }
                }

            }
            catch {}

            // TODO: Also, these items are potential types for conversion.
            // ISharePointProjectFeatureResourceFile
            // ISharePointProjectPackage
            
            return artefacts;
        }


        private static string SubstituteRootTokens(ISharePointProject project, string name)
        {
            name = name.Replace("{ProjectRoot}", Path.GetDirectoryName(project.FullPath)).Replace("\\\\", "\\");
            name = name.Replace("{SharePointRoot}", project.ProjectService.SharePointInstallPath).Replace("\\\\", "\\");
            return name;
        }

        public static void CopyFile(ISharePointProject project, string fileName, string source, string target)
        {
            // Log the copy using the project and hive-relative formats.
            source = Path.Combine(source, fileName);
            target = Path.Combine(target, fileName);
            OutputWindowLogger.Instance.WriteLine(string.Format("{0} -> {1}", source, target), LogCategory.Status);

            // Now replace the project and hive tokens ready for the actual copy.
            string fullSource = SubstituteRootTokens(project, source);
            string fullTarget = SubstituteRootTokens(project, target);

            // Now create the directory and copy the files.
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(fullTarget));
                File.Copy(fullSource, fullTarget, true);
            }
            catch (Exception ex)
            {
                OutputWindowLogger.Instance.WriteLine("EXCEPTION: " + ex.Message, LogCategory.Error);
            }
        }

        public static void CopyFileWithTokenReplacement(ISharePointProject project, string fileName, string source, string target, Dictionary<string, string> tokens)
        {
            // Log the copy using the project and hive-relative formats.
            source = Path.Combine(source, fileName);
            target = Path.Combine(target, fileName);
            //OutputWindowLogger.Instance.WriteLine(string.Format("QP {0} -> {1}", source, target), LogCategory.Status);

            // Now replace the project and hive tokens ready for the actual copy.
            string fullSource = SubstituteRootTokens(project, source);
            string fullTarget = SubstituteRootTokens(project, target);

            // Now create the directory and copy the files.
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(fullTarget));     
           
                if (File.Exists(fullTarget))
                {
                    File.Delete(fullTarget);
                }

                if (tokens == null)
                {
                    File.Copy(fullSource, fullTarget, true);
                }
                else
                {
                    StreamReader reader = new StreamReader(fullSource);
                    StreamWriter writer = File.CreateText(fullTarget);

                    while (!reader.EndOfStream)
                    {
                        string line = reader.ReadLine();

                        // Substitute tokens.
                        foreach (string token in tokens.Keys)
                        {
                            line = line.Replace("$" + token + "$", tokens[token]);
                        }

                        writer.WriteLine(line);
                    }

                    reader.Close();
                    writer.Close();
                }
            }
            catch (Exception ex)
            {
                OutputWindowLogger.Instance.WriteLine("EXCEPTION: " + ex.Message, LogCategory.Error);
            }
        }

        public static void CopyToGac(ISharePointProject project, string fileName)
        {
            try
            {
                OutputWindowLogger.Instance.WriteLine(fileName + " -> GAC", LogCategory.Status);
                AssemblyCache.InstallAssembly(SubstituteRootTokens(project, fileName), null, AssemblyCommitFlags.Force);
            }
            catch (Exception ex)
            {
                OutputWindowLogger.Instance.WriteLine("EXCEPTION: " + ex.Message, LogCategory.Error);
            }
        }

        public static void CopyToBin(ISharePointProject project, string fileName)
        {
            try
            {
                string appPath = QuickDeploymentUtilities.GetWebApplicationPhysicalPath(project.ProjectService, project.SiteUrl.ToString());
                if (String.IsNullOrEmpty(appPath))
                {
                    throw new Exception(string.Format("Unable to retrieve Web Application bin path for URL: {0}", project.SiteUrl.ToString()));
                }
                else
                {
                    string fullSource = QuickDeploymentUtilities.SubstituteRootTokens(project, fileName);
                    string fullDestination = QuickDeploymentUtilities.SubstituteRootTokens(project, Path.Combine(Path.Combine(appPath, "bin"), Path.GetFileName(fileName)));

                    OutputWindowLogger.Instance.WriteLine(fileName + " -> " + fullDestination, LogCategory.Status);

                    // Copy the file, letting exceptions be thrown and caught below (e.g. don't check source path).
                    File.Copy(fullSource, fullDestination, true);
                }

            }
            catch (Exception ex)
            {
                OutputWindowLogger.Instance.WriteLine("EXCEPTION: " + ex.Message, LogCategory.Error);
            }
        }

        public static Assembly GetAssembly(string file)
        {
            if (File.Exists(file))
            {
                return Assembly.LoadFrom(file);
            }
            else
            {
                return null;
            }
        }

        public static string GetPublicKeyToken(string fullAssemblyName)
        {
            string token = String.Empty;
            if (fullAssemblyName.Contains("PublicKeyToken="))
            {
                token = fullAssemblyName.Substring(fullAssemblyName.IndexOf("PublicKeyToken=") + 15);
                if (token == "null")
                {
                    token = String.Empty;
                }
            }
            return token;
        }

        public static string ByteArrayToString(byte[] item)
        {
            return new System.Text.UTF8Encoding().GetString(item);
        }

        public static bool IsTokenReplacementFile(string fileName)
        {
            // For now this is hardcoded - eventually we should retrieve this from the same config file the tooling uses.
            string ext = Path.GetExtension(fileName).ToLower();
            return DefaultTokenizedFiles.Contains(ext);            
        }

        public static string GetWebApplicationPhysicalPath(ISharePointProjectService service, string url)
        {
            string name = service.SharePointConnection.ExecuteCommand<string, string>("SPVSX.Commands.GetWebApplicationDefaultPhysicalPath", url);
            return name;
        }
    }


}